// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/cache_storage/cache_storage.h"

#include <stddef.h>

#include <set>
#include <string>
#include <utility>

#include "base/barrier_closure.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/guid.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/sha1.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/cache_storage/cache_storage.pb.h"
#include "content/browser/cache_storage/cache_storage_cache.h"
#include "content/browser/cache_storage/cache_storage_cache_handle.h"
#include "content/browser/cache_storage/cache_storage_index.h"
#include "content/browser/cache_storage/cache_storage_scheduler.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/directory_lister.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request_context_getter.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/quota/quota_manager_proxy.h"

namespace content {

namespace {

    std::string HexedHash(const std::string& value)
    {
        std::string value_hash = base::SHA1HashString(value);
        std::string valued_hexed_hash = base::ToLowerASCII(
            base::HexEncode(value_hash.c_str(), value_hash.length()));
        return valued_hexed_hash;
    }

    void SizeRetrievedFromAllCaches(std::unique_ptr<int64_t> accumulator,
        const CacheStorage::SizeCallback& callback)
    {
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(callback, *accumulator));
    }

    void DoNothingWithBool(bool success) { }

} // namespace

const char CacheStorage::kIndexFileName[] = "index.txt";
constexpr int64_t CacheStorage::kSizeUnknown;

struct CacheStorage::CacheMatchResponse {
    CacheMatchResponse() = default;
    ~CacheMatchResponse() = default;

    CacheStorageError error;
    std::unique_ptr<ServiceWorkerResponse> service_worker_response;
    std::unique_ptr<storage::BlobDataHandle> blob_data_handle;
};

// Handles the loading and clean up of CacheStorageCache objects.
class CacheStorage::CacheLoader {
public:
    typedef base::Callback<void(std::unique_ptr<CacheStorageCache>)>
        CacheCallback;
    typedef base::Callback<void(bool)> BoolCallback;
    using CacheStorageIndexLoadCallback = base::Callback<void(std::unique_ptr<CacheStorageIndex>)>;

    CacheLoader(
        base::SequencedTaskRunner* cache_task_runner,
        scoped_refptr<net::URLRequestContextGetter> request_context_getter,
        storage::QuotaManagerProxy* quota_manager_proxy,
        base::WeakPtr<storage::BlobStorageContext> blob_context,
        CacheStorage* cache_storage,
        const GURL& origin)
        : cache_task_runner_(cache_task_runner)
        , request_context_getter_(request_context_getter)
        , quota_manager_proxy_(quota_manager_proxy)
        , blob_context_(blob_context)
        , cache_storage_(cache_storage)
        , origin_(origin)
    {
        DCHECK(!origin_.is_empty());
    }

    virtual ~CacheLoader() { }

    // Creates a CacheStorageCache with the given name. It does not attempt to
    // load the backend, that happens lazily when the cache is used.
    virtual std::unique_ptr<CacheStorageCache> CreateCache(
        const std::string& cache_name,
        int64_t cache_size)
        = 0;

    // Deletes any pre-existing cache of the same name and then loads it.
    virtual void PrepareNewCacheDestination(const std::string& cache_name,
        const CacheCallback& callback)
        = 0;

    // After the backend has been deleted, do any extra house keeping such as
    // removing the cache's directory.
    virtual void CleanUpDeletedCache(CacheStorageCache* cache) = 0;

    // Writes the cache index to disk if applicable.
    virtual void WriteIndex(const CacheStorageIndex& index,
        const BoolCallback& callback)
        = 0;

    // Loads the cache index from disk if applicable.
    virtual void LoadIndex(const CacheStorageIndexLoadCallback& callback) = 0;

    // Called when CacheStorage has created a cache. Used to hold onto a handle to
    // the cache if necessary.
    virtual void NotifyCacheCreated(
        const std::string& cache_name,
        std::unique_ptr<CacheStorageCacheHandle> cache_handle) { }

    // Notification that the cache for |cache_handle| has been doomed. If the
    // loader is holding a handle to the cache, it should drop it now.
    virtual void NotifyCacheDoomed(
        std::unique_ptr<CacheStorageCacheHandle> cache_handle) { }

protected:
    scoped_refptr<base::SequencedTaskRunner> cache_task_runner_;
    scoped_refptr<net::URLRequestContextGetter> request_context_getter_;

    // Owned by CacheStorage which owns this.
    storage::QuotaManagerProxy* quota_manager_proxy_;

    base::WeakPtr<storage::BlobStorageContext> blob_context_;

    // Raw pointer is safe because this object is owned by cache_storage_.
    CacheStorage* cache_storage_;

    GURL origin_;
};

// Creates memory-only ServiceWorkerCaches. Because these caches have no
// persistent storage it is not safe to free them from memory if they might be
// used again. Therefore this class holds a reference to each cache until the
// cache is doomed.
class CacheStorage::MemoryLoader : public CacheStorage::CacheLoader {
public:
    MemoryLoader(base::SequencedTaskRunner* cache_task_runner,
        scoped_refptr<net::URLRequestContextGetter> request_context,
        storage::QuotaManagerProxy* quota_manager_proxy,
        base::WeakPtr<storage::BlobStorageContext> blob_context,
        CacheStorage* cache_storage,
        const GURL& origin)
        : CacheLoader(cache_task_runner,
            request_context,
            quota_manager_proxy,
            blob_context,
            cache_storage,
            origin)
    {
    }

    std::unique_ptr<CacheStorageCache> CreateCache(const std::string& cache_name,
        int64_t cache_size) override
    {
        return CacheStorageCache::CreateMemoryCache(
            origin_, cache_name, cache_storage_, request_context_getter_,
            quota_manager_proxy_, blob_context_);
    }

    void PrepareNewCacheDestination(const std::string& cache_name,
        const CacheCallback& callback) override
    {
        std::unique_ptr<CacheStorageCache> cache = CreateCache(cache_name, 0 /*cache_size*/);
        callback.Run(std::move(cache));
    }

    void CleanUpDeletedCache(CacheStorageCache* cache) override { }

    void WriteIndex(const CacheStorageIndex& index,
        const BoolCallback& callback) override
    {
        callback.Run(true);
    }

    void LoadIndex(const CacheStorageIndexLoadCallback& callback) override
    {
        callback.Run(base::MakeUnique<CacheStorageIndex>());
    }

    void NotifyCacheCreated(
        const std::string& cache_name,
        std::unique_ptr<CacheStorageCacheHandle> cache_handle) override
    {
        DCHECK(!base::ContainsKey(cache_handles_, cache_name));
        cache_handles_.insert(std::make_pair(cache_name, std::move(cache_handle)));
    };

    void NotifyCacheDoomed(
        std::unique_ptr<CacheStorageCacheHandle> cache_handle) override
    {
        DCHECK(
            base::ContainsKey(cache_handles_, cache_handle->value()->cache_name()));
        cache_handles_.erase(cache_handle->value()->cache_name());
    };

private:
    typedef std::map<std::string, std::unique_ptr<CacheStorageCacheHandle>>
        CacheHandles;
    ~MemoryLoader() override { }

    // Keep a reference to each cache to ensure that it's not freed before the
    // client calls CacheStorage::Delete or the CacheStorage is
    // freed.
    CacheHandles cache_handles_;
};

class CacheStorage::SimpleCacheLoader : public CacheStorage::CacheLoader {
public:
    SimpleCacheLoader(const base::FilePath& origin_path,
        base::SequencedTaskRunner* cache_task_runner,
        scoped_refptr<net::URLRequestContextGetter> request_context,
        storage::QuotaManagerProxy* quota_manager_proxy,
        base::WeakPtr<storage::BlobStorageContext> blob_context,
        CacheStorage* cache_storage,
        const GURL& origin)
        : CacheLoader(cache_task_runner,
            request_context,
            quota_manager_proxy,
            blob_context,
            cache_storage,
            origin)
        , origin_path_(origin_path)
        , weak_ptr_factory_(this)
    {
    }

    std::unique_ptr<CacheStorageCache> CreateCache(const std::string& cache_name,
        int64_t cache_size) override
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);
        DCHECK(base::ContainsKey(cache_name_to_cache_dir_, cache_name));

        std::string cache_dir = cache_name_to_cache_dir_[cache_name];
        base::FilePath cache_path = origin_path_.AppendASCII(cache_dir);
        return CacheStorageCache::CreatePersistentCache(
            origin_, cache_name, cache_storage_, cache_path,
            request_context_getter_, quota_manager_proxy_, blob_context_,
            cache_size);
    }

    void PrepareNewCacheDestination(const std::string& cache_name,
        const CacheCallback& callback) override
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);

        PostTaskAndReplyWithResult(
            cache_task_runner_.get(), FROM_HERE,
            base::Bind(&SimpleCacheLoader::PrepareNewCacheDirectoryInPool,
                origin_path_),
            base::Bind(&SimpleCacheLoader::PrepareNewCacheCreateCache,
                weak_ptr_factory_.GetWeakPtr(), cache_name, callback));
    }

    // Runs on the cache_task_runner_.
    static std::string PrepareNewCacheDirectoryInPool(
        const base::FilePath& origin_path)
    {
        std::string cache_dir;
        base::FilePath cache_path;
        do {
            cache_dir = base::GenerateGUID();
            cache_path = origin_path.AppendASCII(cache_dir);
        } while (base::PathExists(cache_path));

        return base::CreateDirectory(cache_path) ? cache_dir : "";
    }

    void PrepareNewCacheCreateCache(const std::string& cache_name,
        const CacheCallback& callback,
        const std::string& cache_dir)
    {
        if (cache_dir.empty()) {
            callback.Run(std::unique_ptr<CacheStorageCache>());
            return;
        }

        cache_name_to_cache_dir_[cache_name] = cache_dir;
        callback.Run(CreateCache(cache_name, CacheStorage::kSizeUnknown));
    }

    void CleanUpDeletedCache(CacheStorageCache* cache) override
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);
        DCHECK(base::ContainsKey(doomed_cache_to_path_, cache));

        base::FilePath cache_path = origin_path_.AppendASCII(doomed_cache_to_path_[cache]);
        doomed_cache_to_path_.erase(cache);

        cache_task_runner_->PostTask(
            FROM_HERE, base::Bind(&SimpleCacheLoader::CleanUpDeleteCacheDirInPool, cache_path));
    }

    static void CleanUpDeleteCacheDirInPool(const base::FilePath& cache_path)
    {
        base::DeleteFile(cache_path, true /* recursive */);
    }

    void WriteIndex(const CacheStorageIndex& index,
        const BoolCallback& callback) override
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);

        // 1. Create the index file as a string. (WriteIndex)
        // 2. Write the file to disk. (WriteIndexWriteToFileInPool)

        proto::CacheStorageIndex protobuf_index;
        protobuf_index.set_origin(origin_.spec());

        for (const auto& cache_metadata : index.ordered_cache_metadata()) {
            DCHECK(base::ContainsKey(cache_name_to_cache_dir_, cache_metadata.name));

            proto::CacheStorageIndex::Cache* index_cache = protobuf_index.add_cache();
            index_cache->set_name(cache_metadata.name);
            index_cache->set_cache_dir(cache_name_to_cache_dir_[cache_metadata.name]);
            if (cache_metadata.size == CacheStorage::kSizeUnknown)
                index_cache->clear_size();
            else
                index_cache->set_size(cache_metadata.size);
        }

        std::string serialized;
        bool success = protobuf_index.SerializeToString(&serialized);
        DCHECK(success);

        base::FilePath tmp_path = origin_path_.AppendASCII("index.txt.tmp");
        base::FilePath index_path = origin_path_.AppendASCII(CacheStorage::kIndexFileName);

        PostTaskAndReplyWithResult(
            cache_task_runner_.get(), FROM_HERE,
            base::Bind(&SimpleCacheLoader::WriteIndexWriteToFileInPool, tmp_path,
                index_path, serialized),
            callback);
    }

    static bool WriteIndexWriteToFileInPool(const base::FilePath& tmp_path,
        const base::FilePath& index_path,
        const std::string& data)
    {
        int bytes_written = base::WriteFile(tmp_path, data.c_str(), data.size());
        if (bytes_written != base::checked_cast<int>(data.size())) {
            base::DeleteFile(tmp_path, /* recursive */ false);
            return false;
        }

        // Atomically rename the temporary index file to become the real one.
        return base::ReplaceFile(tmp_path, index_path, NULL);
    }

    void LoadIndex(const CacheStorageIndexLoadCallback& callback) override
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);

        PostTaskAndReplyWithResult(
            cache_task_runner_.get(), FROM_HERE,
            base::Bind(&SimpleCacheLoader::ReadAndMigrateIndexInPool, origin_path_),
            base::Bind(&SimpleCacheLoader::LoadIndexDidReadIndex,
                weak_ptr_factory_.GetWeakPtr(), callback));
    }

    void LoadIndexDidReadIndex(const CacheStorageIndexLoadCallback& callback,
        proto::CacheStorageIndex protobuf_index)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);

        std::unique_ptr<std::set<std::string>> cache_dirs(
            new std::set<std::string>);

        auto index = base::MakeUnique<CacheStorageIndex>();
        for (int i = 0, max = protobuf_index.cache_size(); i < max; ++i) {
            const proto::CacheStorageIndex::Cache& cache = protobuf_index.cache(i);
            DCHECK(cache.has_cache_dir());
            int64_t cache_size = cache.has_size() ? cache.size() : CacheStorage::kSizeUnknown;
            index->Insert(CacheStorageIndex::CacheMetadata(cache.name(), cache_size));
            cache_name_to_cache_dir_[cache.name()] = cache.cache_dir();
            cache_dirs->insert(cache.cache_dir());
        }

        cache_task_runner_->PostTask(
            FROM_HERE, base::Bind(&DeleteUnreferencedCachesInPool, origin_path_, base::Passed(&cache_dirs)));
        callback.Run(std::move(index));
    }

    void NotifyCacheDoomed(
        std::unique_ptr<CacheStorageCacheHandle> cache_handle) override
    {
        DCHECK(base::ContainsKey(cache_name_to_cache_dir_,
            cache_handle->value()->cache_name()));
        auto iter = cache_name_to_cache_dir_.find(cache_handle->value()->cache_name());
        doomed_cache_to_path_[cache_handle->value()] = iter->second;
        cache_name_to_cache_dir_.erase(iter);
    };

private:
    friend class MigratedLegacyCacheDirectoryNameTest;
    ~SimpleCacheLoader() override { }

    // Iterates over the caches and deletes any directory not found in
    // |cache_dirs|. Runs on cache_task_runner_
    static void DeleteUnreferencedCachesInPool(
        const base::FilePath& cache_base_dir,
        std::unique_ptr<std::set<std::string>> cache_dirs)
    {
        base::FileEnumerator file_enum(cache_base_dir, false /* recursive */,
            base::FileEnumerator::DIRECTORIES);
        std::vector<base::FilePath> dirs_to_delete;
        base::FilePath cache_path;
        while (!(cache_path = file_enum.Next()).empty()) {
            if (!base::ContainsKey(*cache_dirs, cache_path.BaseName().AsUTF8Unsafe()))
                dirs_to_delete.push_back(cache_path);
        }

        for (const base::FilePath& cache_path : dirs_to_delete)
            base::DeleteFile(cache_path, true /* recursive */);
    }

    // Runs on cache_task_runner_
    static proto::CacheStorageIndex ReadAndMigrateIndexInPool(
        const base::FilePath& origin_path)
    {
        const base::FilePath index_path = origin_path.AppendASCII(CacheStorage::kIndexFileName);

        proto::CacheStorageIndex index;
        std::string body;
        if (!base::ReadFileToString(index_path, &body) || !index.ParseFromString(body))
            return proto::CacheStorageIndex();
        body.clear();

        base::File::Info file_info;
        base::Time index_last_modified;
        if (GetFileInfo(index_path, &file_info))
            index_last_modified = file_info.last_modified;
        bool index_modified = false;

        // Look for caches that have no cache_dir. Give any such caches a directory
        // with a random name and move them there. Then, rewrite the index file.
        // Additionally invalidate the size of any index entries where the cache was
        // modified after the index (making it out-of-date).
        for (int i = 0, max = index.cache_size(); i < max; ++i) {
            const proto::CacheStorageIndex::Cache& cache = index.cache(i);
            if (cache.has_cache_dir()) {
                if (cache.has_size()) {
                    base::FilePath cache_dir = origin_path.AppendASCII(cache.cache_dir());
                    if (!GetFileInfo(cache_dir, &file_info) || index_last_modified <= file_info.last_modified) {
                        // Index is older than this cache, so invalidate index entries that
                        // may change as a result of cache operations.
                        index.mutable_cache(i)->clear_size();
                    }
                }
            } else {
                // Find a new home for the cache.
                base::FilePath legacy_cache_path = origin_path.AppendASCII(HexedHash(cache.name()));
                std::string cache_dir;
                base::FilePath cache_path;
                do {
                    cache_dir = base::GenerateGUID();
                    cache_path = origin_path.AppendASCII(cache_dir);
                } while (base::PathExists(cache_path));

                if (!base::Move(legacy_cache_path, cache_path)) {
                    // If the move fails then the cache is in a bad state. Return an empty
                    // index so that the CacheStorage can start fresh. The unreferenced
                    // caches will be discarded later in initialization.
                    return proto::CacheStorageIndex();
                }

                index.mutable_cache(i)->set_cache_dir(cache_dir);
                index.mutable_cache(i)->clear_size();
                index_modified = true;
            }
        }

        if (index_modified) {
            if (!index.SerializeToString(&body))
                return proto::CacheStorageIndex();
            if (base::WriteFile(index_path, body.c_str(), body.size()) != base::checked_cast<int>(body.size()))
                return proto::CacheStorageIndex();
        }

        return index;
    }

    const base::FilePath origin_path_;
    std::map<std::string, std::string> cache_name_to_cache_dir_;
    std::map<CacheStorageCache*, std::string> doomed_cache_to_path_;

    base::WeakPtrFactory<SimpleCacheLoader> weak_ptr_factory_;
};

CacheStorage::CacheStorage(
    const base::FilePath& path,
    bool memory_only,
    base::SequencedTaskRunner* cache_task_runner,
    scoped_refptr<net::URLRequestContextGetter> request_context,
    scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
    base::WeakPtr<storage::BlobStorageContext> blob_context,
    const GURL& origin)
    : initialized_(false)
    , initializing_(false)
    , memory_only_(memory_only)
    , scheduler_(new CacheStorageScheduler(
          CacheStorageSchedulerClient::CLIENT_STORAGE))
    , origin_path_(path)
    , cache_task_runner_(cache_task_runner)
    , quota_manager_proxy_(quota_manager_proxy)
    , origin_(origin)
    , weak_factory_(this)
{
    if (memory_only)
        cache_loader_.reset(new MemoryLoader(
            cache_task_runner_.get(), std::move(request_context),
            quota_manager_proxy.get(), blob_context, this, origin));
    else
        cache_loader_.reset(new SimpleCacheLoader(
            origin_path_, cache_task_runner_.get(), std::move(request_context),
            quota_manager_proxy.get(), blob_context, this, origin));
}

CacheStorage::~CacheStorage()
{
}

void CacheStorage::OpenCache(const std::string& cache_name,
    const CacheAndErrorCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    if (!initialized_)
        LazyInit();

    quota_manager_proxy_->NotifyStorageAccessed(
        storage::QuotaClient::kServiceWorkerCache, origin_,
        storage::kStorageTypeTemporary);

    scheduler_->ScheduleOperation(
        base::Bind(&CacheStorage::OpenCacheImpl, weak_factory_.GetWeakPtr(),
            cache_name, scheduler_->WrapCallbackToRunNext(callback)));
}

void CacheStorage::HasCache(const std::string& cache_name,
    const BoolAndErrorCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    if (!initialized_)
        LazyInit();

    quota_manager_proxy_->NotifyStorageAccessed(
        storage::QuotaClient::kServiceWorkerCache, origin_,
        storage::kStorageTypeTemporary);

    scheduler_->ScheduleOperation(
        base::Bind(&CacheStorage::HasCacheImpl, weak_factory_.GetWeakPtr(),
            cache_name, scheduler_->WrapCallbackToRunNext(callback)));
}

void CacheStorage::DeleteCache(const std::string& cache_name,
    const BoolAndErrorCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    if (!initialized_)
        LazyInit();

    quota_manager_proxy_->NotifyStorageAccessed(
        storage::QuotaClient::kServiceWorkerCache, origin_,
        storage::kStorageTypeTemporary);

    scheduler_->ScheduleOperation(
        base::Bind(&CacheStorage::DeleteCacheImpl, weak_factory_.GetWeakPtr(),
            cache_name, scheduler_->WrapCallbackToRunNext(callback)));
}

void CacheStorage::EnumerateCaches(const IndexCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    if (!initialized_)
        LazyInit();

    quota_manager_proxy_->NotifyStorageAccessed(
        storage::QuotaClient::kServiceWorkerCache, origin_,
        storage::kStorageTypeTemporary);

    scheduler_->ScheduleOperation(
        base::Bind(&CacheStorage::EnumerateCachesImpl, weak_factory_.GetWeakPtr(),
            scheduler_->WrapCallbackToRunNext(callback)));
}

void CacheStorage::MatchCache(
    const std::string& cache_name,
    std::unique_ptr<ServiceWorkerFetchRequest> request,
    const CacheStorageCacheQueryParams& match_params,
    const CacheStorageCache::ResponseCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    if (!initialized_)
        LazyInit();

    quota_manager_proxy_->NotifyStorageAccessed(
        storage::QuotaClient::kServiceWorkerCache, origin_,
        storage::kStorageTypeTemporary);

    scheduler_->ScheduleOperation(
        base::Bind(&CacheStorage::MatchCacheImpl, weak_factory_.GetWeakPtr(),
            cache_name, base::Passed(std::move(request)), match_params,
            scheduler_->WrapCallbackToRunNext(callback)));
}

void CacheStorage::MatchAllCaches(
    std::unique_ptr<ServiceWorkerFetchRequest> request,
    const CacheStorageCacheQueryParams& match_params,
    const CacheStorageCache::ResponseCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    if (!initialized_)
        LazyInit();

    quota_manager_proxy_->NotifyStorageAccessed(
        storage::QuotaClient::kServiceWorkerCache, origin_,
        storage::kStorageTypeTemporary);

    scheduler_->ScheduleOperation(
        base::Bind(&CacheStorage::MatchAllCachesImpl, weak_factory_.GetWeakPtr(),
            base::Passed(std::move(request)), match_params,
            scheduler_->WrapCallbackToRunNext(callback)));
}

void CacheStorage::GetSizeThenCloseAllCaches(const SizeCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    if (!initialized_)
        LazyInit();

    scheduler_->ScheduleOperation(base::Bind(
        &CacheStorage::GetSizeThenCloseAllCachesImpl, weak_factory_.GetWeakPtr(),
        scheduler_->WrapCallbackToRunNext(callback)));
}

void CacheStorage::Size(const CacheStorage::SizeCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    if (!initialized_)
        LazyInit();

    scheduler_->ScheduleOperation(
        base::Bind(&CacheStorage::SizeImpl, weak_factory_.GetWeakPtr(),
            scheduler_->WrapCallbackToRunNext(callback)));
}

void CacheStorage::ScheduleWriteIndex()
{
    static const int64_t kWriteIndexDelaySecs = 5;
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    index_write_task_.Reset(base::Bind(&CacheStorage::WriteIndex,
        weak_factory_.GetWeakPtr(),
        base::Bind(&DoNothingWithBool)));
    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE, index_write_task_.callback(),
        base::TimeDelta::FromSeconds(kWriteIndexDelaySecs));
}

void CacheStorage::WriteIndex(const base::Callback<void(bool)>& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    scheduler_->ScheduleOperation(
        base::Bind(&CacheStorage::WriteIndexImpl, weak_factory_.GetWeakPtr(),
            scheduler_->WrapCallbackToRunNext(callback)));
}

void CacheStorage::WriteIndexImpl(const base::Callback<void(bool)>& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    cache_loader_->WriteIndex(*cache_index_, callback);
}

bool CacheStorage::InitiateScheduledIndexWriteForTest(
    const base::Callback<void(bool)>& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    if (index_write_pending()) {
        index_write_task_.Cancel();
        WriteIndex(callback);
        return true;
    }
    callback.Run(true /* success */);
    return false;
}

void CacheStorage::CacheSizeUpdated(const CacheStorageCache* cache,
    int64_t size)
{
    // Should not be called for doomed caches.
    DCHECK(!base::ContainsKey(doomed_caches_,
        const_cast<CacheStorageCache*>(cache)));
    cache_index_->SetCacheSize(cache->cache_name(), size);
    ScheduleWriteIndex();
}

void CacheStorage::StartAsyncOperationForTesting()
{
    scheduler_->ScheduleOperation(base::Bind(&base::DoNothing));
}

void CacheStorage::CompleteAsyncOperationForTesting()
{
    scheduler_->CompleteOperationAndRunNext();
}

// Init is run lazily so that it is called on the proper MessageLoop.
void CacheStorage::LazyInit()
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    DCHECK(!initialized_);

    if (initializing_)
        return;

    DCHECK(!scheduler_->ScheduledOperations());

    initializing_ = true;
    scheduler_->ScheduleOperation(
        base::Bind(&CacheStorage::LazyInitImpl, weak_factory_.GetWeakPtr()));
}

void CacheStorage::LazyInitImpl()
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    DCHECK(!initialized_);
    DCHECK(initializing_);

    // 1. Get the cache index (async call)
    // 2. For each cache name, load the cache (async call)
    // 3. Once each load is complete, update the map variables.
    // 4. Call the list of waiting callbacks.

    cache_loader_->LoadIndex(base::Bind(&CacheStorage::LazyInitDidLoadIndex,
        weak_factory_.GetWeakPtr()));
}

void CacheStorage::LazyInitDidLoadIndex(
    std::unique_ptr<CacheStorageIndex> index)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    DCHECK(cache_map_.empty());

    for (const auto& cache_metadata : index->ordered_cache_metadata()) {
        cache_map_.insert(std::make_pair(cache_metadata.name,
            std::unique_ptr<CacheStorageCache>()));
    }

    DCHECK(!cache_index_);
    cache_index_ = std::move(index);

    initializing_ = false;
    initialized_ = true;

    scheduler_->CompleteOperationAndRunNext();
}

void CacheStorage::OpenCacheImpl(const std::string& cache_name,
    const CacheAndErrorCallback& callback)
{
    std::unique_ptr<CacheStorageCacheHandle> cache_handle = GetLoadedCache(cache_name);
    if (cache_handle) {
        callback.Run(std::move(cache_handle), CACHE_STORAGE_OK);
        return;
    }

    cache_loader_->PrepareNewCacheDestination(
        cache_name, base::Bind(&CacheStorage::CreateCacheDidCreateCache, weak_factory_.GetWeakPtr(), cache_name, callback));
}

void CacheStorage::CreateCacheDidCreateCache(
    const std::string& cache_name,
    const CacheAndErrorCallback& callback,
    std::unique_ptr<CacheStorageCache> cache)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    UMA_HISTOGRAM_BOOLEAN("ServiceWorkerCache.CreateCacheStorageResult",
        static_cast<bool>(cache));

    if (!cache) {
        callback.Run(std::unique_ptr<CacheStorageCacheHandle>(),
            CACHE_STORAGE_ERROR_STORAGE);
        return;
    }

    CacheStorageCache* cache_ptr = cache.get();

    cache_map_.insert(std::make_pair(cache_name, std::move(cache)));
    cache_index_->Insert(
        CacheStorageIndex::CacheMetadata(cache_name, cache_ptr->cache_size()));

    cache_loader_->WriteIndex(
        *cache_index_, base::Bind(&CacheStorage::CreateCacheDidWriteIndex, weak_factory_.GetWeakPtr(), callback, base::Passed(CreateCacheHandle(cache_ptr))));

    cache_loader_->NotifyCacheCreated(cache_name, CreateCacheHandle(cache_ptr));
}

void CacheStorage::CreateCacheDidWriteIndex(
    const CacheAndErrorCallback& callback,
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    bool success)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    DCHECK(cache_handle);

    // TODO(jkarlin): Handle !success.

    callback.Run(std::move(cache_handle), CACHE_STORAGE_OK);
}

void CacheStorage::HasCacheImpl(const std::string& cache_name,
    const BoolAndErrorCallback& callback)
{
    bool has_cache = base::ContainsKey(cache_map_, cache_name);
    callback.Run(has_cache, CACHE_STORAGE_OK);
}

void CacheStorage::DeleteCacheImpl(const std::string& cache_name,
    const BoolAndErrorCallback& callback)
{
    std::unique_ptr<CacheStorageCacheHandle> cache_handle = GetLoadedCache(cache_name);
    if (!cache_handle) {
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(callback, false, CACHE_STORAGE_ERROR_NOT_FOUND));
        return;
    }

    cache_handle->value()->SetObserver(nullptr);
    cache_index_->DoomCache(cache_name);
    cache_loader_->WriteIndex(
        *cache_index_,
        base::Bind(&CacheStorage::DeleteCacheDidWriteIndex,
            weak_factory_.GetWeakPtr(),
            base::Passed(std::move(cache_handle)), callback));
}

void CacheStorage::DeleteCacheDidWriteIndex(
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    const BoolAndErrorCallback& callback,
    bool success)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    if (!success) {
        // Undo any changes if the index couldn't be written to disk.
        cache_index_->RestoreDoomedCache();
        cache_handle->value()->SetObserver(this);
        callback.Run(false, CACHE_STORAGE_ERROR_STORAGE);
        return;
    }

    cache_index_->FinalizeDoomedCache();

    CacheMap::iterator map_iter = cache_map_.find(cache_handle->value()->cache_name());
    DCHECK(map_iter != cache_map_.end());

    doomed_caches_.insert(
        std::make_pair(map_iter->second.get(), std::move(map_iter->second)));
    cache_map_.erase(map_iter);

    cache_loader_->NotifyCacheDoomed(std::move(cache_handle));

    callback.Run(true, CACHE_STORAGE_OK);
}

// Call this once the last handle to a doomed cache is gone. It's okay if this
// doesn't get to complete before shutdown, the cache will be removed from disk
// on next startup in that case.
void CacheStorage::DeleteCacheFinalize(CacheStorageCache* doomed_cache)
{
    doomed_cache->Size(base::Bind(&CacheStorage::DeleteCacheDidGetSize,
        weak_factory_.GetWeakPtr(), doomed_cache));
}

void CacheStorage::DeleteCacheDidGetSize(CacheStorageCache* doomed_cache,
    int64_t cache_size)
{
    quota_manager_proxy_->NotifyStorageModified(
        storage::QuotaClient::kServiceWorkerCache, origin_,
        storage::kStorageTypeTemporary, -1 * cache_size);

    cache_loader_->CleanUpDeletedCache(doomed_cache);
    auto doomed_caches_iter = doomed_caches_.find(doomed_cache);
    DCHECK(doomed_caches_iter != doomed_caches_.end());
    doomed_caches_.erase(doomed_caches_iter);
}

void CacheStorage::EnumerateCachesImpl(const IndexCallback& callback)
{
    callback.Run(*cache_index_);
}

void CacheStorage::MatchCacheImpl(
    const std::string& cache_name,
    std::unique_ptr<ServiceWorkerFetchRequest> request,
    const CacheStorageCacheQueryParams& match_params,
    const CacheStorageCache::ResponseCallback& callback)
{
    std::unique_ptr<CacheStorageCacheHandle> cache_handle = GetLoadedCache(cache_name);

    if (!cache_handle) {
        callback.Run(CACHE_STORAGE_ERROR_CACHE_NAME_NOT_FOUND,
            std::unique_ptr<ServiceWorkerResponse>(),
            std::unique_ptr<storage::BlobDataHandle>());
        return;
    }

    // Pass the cache handle along to the callback to keep the cache open until
    // match is done.
    CacheStorageCache* cache_ptr = cache_handle->value();
    cache_ptr->Match(
        std::move(request), match_params,
        base::Bind(&CacheStorage::MatchCacheDidMatch, weak_factory_.GetWeakPtr(),
            base::Passed(std::move(cache_handle)), callback));
}

void CacheStorage::MatchCacheDidMatch(
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    const CacheStorageCache::ResponseCallback& callback,
    CacheStorageError error,
    std::unique_ptr<ServiceWorkerResponse> response,
    std::unique_ptr<storage::BlobDataHandle> handle)
{
    callback.Run(error, std::move(response), std::move(handle));
}

void CacheStorage::MatchAllCachesImpl(
    std::unique_ptr<ServiceWorkerFetchRequest> request,
    const CacheStorageCacheQueryParams& match_params,
    const CacheStorageCache::ResponseCallback& callback)
{
    std::vector<CacheMatchResponse>* match_responses = new std::vector<CacheMatchResponse>(cache_index_->num_entries());

    base::Closure barrier_closure = base::BarrierClosure(
        cache_index_->num_entries(),
        base::Bind(&CacheStorage::MatchAllCachesDidMatchAll,
            weak_factory_.GetWeakPtr(),
            base::Passed(base::WrapUnique(match_responses)), callback));

    size_t idx = 0;
    for (const auto& cache_metadata : cache_index_->ordered_cache_metadata()) {
        std::unique_ptr<CacheStorageCacheHandle> cache_handle = GetLoadedCache(cache_metadata.name);
        DCHECK(cache_handle);

        CacheStorageCache* cache_ptr = cache_handle->value();
        cache_ptr->Match(base::MakeUnique<ServiceWorkerFetchRequest>(*request),
            match_params,
            base::Bind(&CacheStorage::MatchAllCachesDidMatch,
                weak_factory_.GetWeakPtr(),
                base::Passed(std::move(cache_handle)),
                &match_responses->at(idx), barrier_closure));
        idx++;
    }
}

void CacheStorage::MatchAllCachesDidMatch(
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    CacheMatchResponse* out_match_response,
    const base::Closure& barrier_closure,
    CacheStorageError error,
    std::unique_ptr<ServiceWorkerResponse> service_worker_response,
    std::unique_ptr<storage::BlobDataHandle> handle)
{
    out_match_response->error = error;
    out_match_response->service_worker_response = std::move(service_worker_response);
    out_match_response->blob_data_handle = std::move(handle);
    barrier_closure.Run();
}

void CacheStorage::MatchAllCachesDidMatchAll(
    std::unique_ptr<std::vector<CacheMatchResponse>> match_responses,
    const CacheStorageCache::ResponseCallback& callback)
{
    for (CacheMatchResponse& match_response : *match_responses) {
        if (match_response.error == CACHE_STORAGE_ERROR_NOT_FOUND)
            continue;
        callback.Run(match_response.error,
            std::move(match_response.service_worker_response),
            std::move(match_response.blob_data_handle));
        return;
    }
    callback.Run(CACHE_STORAGE_ERROR_NOT_FOUND,
        std::unique_ptr<ServiceWorkerResponse>(),
        std::unique_ptr<storage::BlobDataHandle>());
}

void CacheStorage::AddCacheHandleRef(CacheStorageCache* cache)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    auto iter = cache_handle_counts_.find(cache);
    if (iter == cache_handle_counts_.end()) {
        cache_handle_counts_[cache] = 1;
        return;
    }

    iter->second += 1;
}

void CacheStorage::DropCacheHandleRef(CacheStorageCache* cache)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    auto iter = cache_handle_counts_.find(cache);
    DCHECK(iter != cache_handle_counts_.end());
    DCHECK_GE(iter->second, 1U);

    iter->second -= 1;
    if (iter->second == 0) {
        cache_handle_counts_.erase(iter);
        auto doomed_caches_iter = doomed_caches_.find(cache);
        if (doomed_caches_iter != doomed_caches_.end()) {
            // The last reference to a doomed cache is gone, perform clean up.
            DeleteCacheFinalize(cache);
            return;
        }

        auto cache_map_iter = cache_map_.find(cache->cache_name());
        DCHECK(cache_map_iter != cache_map_.end());

        cache_map_iter->second.reset();
    }
}

std::unique_ptr<CacheStorageCacheHandle> CacheStorage::CreateCacheHandle(
    CacheStorageCache* cache)
{
    DCHECK(cache);
    return std::unique_ptr<CacheStorageCacheHandle>(new CacheStorageCacheHandle(
        cache->AsWeakPtr(), weak_factory_.GetWeakPtr()));
}

std::unique_ptr<CacheStorageCacheHandle> CacheStorage::GetLoadedCache(
    const std::string& cache_name)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    DCHECK(initialized_);

    CacheMap::iterator map_iter = cache_map_.find(cache_name);
    if (map_iter == cache_map_.end())
        return std::unique_ptr<CacheStorageCacheHandle>();

    CacheStorageCache* cache = map_iter->second.get();

    if (!cache) {
        std::unique_ptr<CacheStorageCache> new_cache = cache_loader_->CreateCache(
            cache_name, cache_index_->GetCacheSize(cache_name));
        CacheStorageCache* cache_ptr = new_cache.get();
        map_iter->second = std::move(new_cache);

        return CreateCacheHandle(cache_ptr);
    }

    return CreateCacheHandle(cache);
}

void CacheStorage::SizeRetrievedFromCache(
    std::unique_ptr<CacheStorageCacheHandle> cache_handle,
    const base::Closure& closure,
    int64_t* accumulator,
    int64_t size)
{
    cache_index_->SetCacheSize(cache_handle->value()->cache_name(), size);
    *accumulator += size;
    closure.Run();
}

void CacheStorage::GetSizeThenCloseAllCachesImpl(const SizeCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    DCHECK(initialized_);

    std::unique_ptr<int64_t> accumulator(new int64_t(0));
    int64_t* accumulator_ptr = accumulator.get();

    base::Closure barrier_closure = base::BarrierClosure(
        cache_index_->num_entries(),
        base::Bind(&SizeRetrievedFromAllCaches,
            base::Passed(std::move(accumulator)), callback));

    for (const auto& cache_metadata : cache_index_->ordered_cache_metadata()) {
        auto cache_handle = GetLoadedCache(cache_metadata.name);
        CacheStorageCache* cache = cache_handle->value();
        cache->GetSizeThenClose(base::Bind(&CacheStorage::SizeRetrievedFromCache,
            weak_factory_.GetWeakPtr(),
            base::Passed(std::move(cache_handle)),
            barrier_closure, accumulator_ptr));
    }
}

void CacheStorage::SizeImpl(const SizeCallback& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    DCHECK(initialized_);

    if (cache_index_->GetStorageSize() != kSizeUnknown) {
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(callback, cache_index_->GetStorageSize()));
        return;
    }

    std::unique_ptr<int64_t> accumulator(new int64_t(0));
    int64_t* accumulator_ptr = accumulator.get();

    base::Closure barrier_closure = base::BarrierClosure(
        cache_index_->num_entries(),
        base::Bind(&SizeRetrievedFromAllCaches,
            base::Passed(std::move(accumulator)), callback));

    for (const auto& cache_metadata : cache_index_->ordered_cache_metadata()) {
        if (cache_metadata.size != CacheStorage::kSizeUnknown) {
            *accumulator_ptr += cache_metadata.size;
            barrier_closure.Run();
            continue;
        }
        std::unique_ptr<CacheStorageCacheHandle> cache_handle = GetLoadedCache(cache_metadata.name);
        CacheStorageCache* cache = cache_handle->value();
        cache->Size(base::Bind(&CacheStorage::SizeRetrievedFromCache,
            weak_factory_.GetWeakPtr(),
            base::Passed(std::move(cache_handle)),
            barrier_closure, accumulator_ptr));
    }
}

} // namespace content
